/* processor.h implements text processing in the asciiboard engine.  The
   processor is designed to allow the user to choose, and program their own
   text processing functions to execute when the engine runs.

   For maximum flexibility, the user passes chosen functions via function
   pointers to the processor, which are added to a dynamic list of function
   pointers.  When a character is sent to the processor, it will simply
   run through every function in the list.  There is a timeout flag, which
   if set, prevents any more processing functions being executed.  
   
   The list of callback functions will essentially execute all functions in
   parallel.  However, if you want your function to pass its output to other
   processing functions, you can simply call them within your function ie
   processing functions may call other processing functions.

   Note that if writing a function for the processor, the function must receive
   the character to be processed as a const char, and return an integer error
   code (defined in asciiboard_const.h)  All interfacing should be done using
   the asciiboard functions.  For the function, consider implementing something
   for the DISABLE_CODE and the RESET_CODE.
   
   Copyright (C) 2014 James Boyle

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

#include "processor.h"
#include "asciiboard.h"
#include "asciiboard_const.h"
#include <stddef.h>
#include <malloc.h>

/* static function forward declarations */
static void func_list_clear(PROCESSOR *const p);
static void func_list_add(PROCESSOR *const p, USER_FUNC *const);

/* processor_init() must be called before using the text processor */
void processor_init(PROCESSOR *const p, int (*tf)(void))
{
	p->func_count = 0;
	p->start = NULL;
	p->timeout_function = tf;
}

/* procesor_finish() should be called when the processor is no longer
   needed, returning any dynamically allocated memory. */
void processor_finish(PROCESSOR *const p)
{
	func_list_clear(p);
}

/* process() receives a character input, and calls every function in the
   function list with it.  Higher priority functions are called first.
   If the timeout flag is set to a nonzero value, any further processing
   stops.  This may be need in realtime audio applications where processing
   time can be limited. */
int process(PROCESSOR *const p, const char ch)
{
	USER_FUNC *curr = p->start;
	int error = NO_ERROR;

	/* iterate through the list of functions, while the user hasn't set the
	   timeout flag */
	while (error == NO_ERROR && curr != NULL)
	{
		/* check to see if processing has timed out, ie too much time is being
		   spent on this character and samples aren't being generated fast 
		   enough */
		if (p->timeout_function != NULL)
		{
			error = p->timeout_function();
			if (error)
				break;
		}

		/* call the function, passing any user supplied arguments */
		error = curr->func(ch);
		curr = curr->next;
	}

	return error;
}

/* processor_add_func() adds a new callback function to the list of processor
   functions.  A priority should be given, so more important processing can
   be done first. */
int processor_add_func(PROCESSOR *const p, int (*f)(const char),
                       const int priority)
{
	USER_FUNC *new_node;

	if (f == NULL)
		return INVALID_ARG;

	new_node = (USER_FUNC *) malloc(sizeof(USER_FUNC));
	if (new_node == NULL)
		return INSUFFICIENT_MEM;
	new_node->func = f;
	new_node->priority = priority;

	func_list_add(p, new_node);
	return NO_ERROR;
}

/* func_list_clear() frees the any memory held by the list of callback
   functions and clears the list. */
static void func_list_clear(PROCESSOR *const p)
{
	/* go through entire list and deallocate any nodes */
	USER_FUNC *next, *curr = p->start;
	while (curr != NULL)
	{
		next = curr->next;
		free(curr);
		curr = next;
	}

	/* reset the list */
	p->start = NULL;
	p->func_count = 0;
}

/* func_list_add() adds a new function to the list, making sure a higher
   priority function will come before a lower priority function.  If there
   are other functions of the same priority in the list, the new function
   will be inserted at the front of that block. */
static void func_list_add(PROCESSOR *const p, USER_FUNC *const new_node)
{
	USER_FUNC *curr = p->start, *prev = NULL;

	p->func_count++;

	/* iterate through the list, when a node of the same or lower priority
	   is found, or the end of the list is reached, link the new node in
	   before these lower priority nodes */
	while(curr != NULL)
	{
		/* note there is a special case for priority LAST, these functions
		   must always be inserted at the end of the list */
		if (new_node->priority != LAST &&
		    curr->priority <= new_node->priority)
			break;
		prev = curr;
		curr = curr->next;
	}

	/* check for the special case of insertion at the front of the list */
	if (prev == NULL)
	{
		new_node->next = curr;
		p->start = new_node;
	}
	else
	{
		prev->next = new_node;
		new_node->next = curr;
	}
}
